// ============================================================================
// FileName: StatelefulProxyCore.cs
//
// Description:
// Stateful proxy core for MySIPSwitch service.
//
// Author(s):
// Aaron Clauson
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006-2008 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
// ============================================================================

using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using BlueFace.Sys;
using BlueFace.Sys.Net;
using BlueFace.VoIP.Authentication;
using BlueFace.VoIP.Net;
using BlueFace.VoIP.Net.App;
using BlueFace.VoIP.Net.SIP;
using BlueFace.VoIP.SIPServer;
using BlueFace.VoIP.SIPServerCores.StatefulProxy.DialPlanApps;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace BlueFace.VoIP.SIPServerCores.StatefulProxy
{    
	public delegate SIPDialPlan LoadDialPlanDelegate(string sipAccountUsername, string sipAccountDomain);
    public delegate SIPAccount LoadSIPAccountDelegate(string username, string domain);
    public delegate string GetExtensionOwnerDelegate(string user, string domain);
    public delegate List<SIPAddressBinding> GetSIPAccountBindingsDelegate(string user, string domain);
    public delegate string GetCanonicalDomainDelegate(string domain);
    public delegate bool DoesSIPAccountExistDelegate(SIPURI uri);

    public class StatefulProxyCore
	{
        public const string SIPPROXY_USERAGENT = "www.sipsorcery.com";
        public const string DEFAULT_DOMAIN = "sip.mysipswitch.com";
               
        private static ILog logger = AppState.GetLogger("sipproxy");

        private readonly string m_proxyViaParameterName = RegistrarCore.PROXY_VIA_PARAMETER_NAME;

        private LoadDialPlanDelegate LoadDialPlan;                     // Function to load user dial plans.
        private LoadSIPAccountDelegate LoadSIPAccount;                 // Function in authenticate user outgoing calls.
        private ProxyLogDelegate StatefulProxyLogEvent;                // Function to log messages from this core.
        private GetExtensionOwnerDelegate GetExtensionOwner;           // Function to look up the owner of an incoming call.
        private GetSIPAccountBindingsDelegate GetSIPAccountBindings;   // Function to lookup bindings that have been registered for a SIP account.
        private DoesSIPAccountExistDelegate DoesSIPAccountExist;       // Function to check whether a SIP account exists.

        private bool m_manglePrivateAddresses = false;
        private SIPTransport m_sipTransport;
        private IPEndPoint m_registrarSocket;
        private IPEndPoint m_regAgentSocket;
        private CallManager m_callManager;
        private DialogueBridgeCreatedDelegate m_createBridgeDelegate;
        
        private SIPDomains m_sipDomains;
        public SIPDomains ProxySIPDomains
        {
            get { return m_sipDomains; }
            set { m_sipDomains = value; }
        }

		public StatefulProxyCore(
			SIPTransport sipTransport, 
            IPEndPoint registrarSocket,
            IPEndPoint regAgentSocket,
            bool manglePrivateAddress,
            SIPDomains sipDomains,
            LoadDialPlanDelegate loadDialPlan,
            LoadSIPAccountDelegate loadSIPAccount,
            GetExtensionOwnerDelegate getExtensionOwner,
            GetSIPAccountBindingsDelegate getSIPAccountBindings,
            DoesSIPAccountExistDelegate doesSIPAccountExist,
            ProxyLogDelegate proxyLog)
		{
			try
			{
                m_sipTransport = sipTransport;
                m_manglePrivateAddresses = manglePrivateAddress;
                m_callManager = new CallManager(m_sipTransport, m_sipTransport.GetDefaultTransportContact(SIPProtocolsEnum.UDP), proxyLog);
                m_createBridgeDelegate = new DialogueBridgeCreatedDelegate(m_callManager.CreateDialogueBridge);

                m_sipTransport.SIPTransportRequestReceived += new SIPTransportRequestReceivedDelegate(GotRequest); 
                m_sipTransport.SIPTransportResponseReceived += new SIPTransportResponseReceivedDelegate(GotResponse);

                m_registrarSocket = registrarSocket;
                m_regAgentSocket = regAgentSocket;
                m_sipDomains = sipDomains;

                LoadDialPlan = loadDialPlan;
                LoadSIPAccount = loadSIPAccount;
                GetExtensionOwner = getExtensionOwner;
                GetSIPAccountBindings = getSIPAccountBindings;
                DoesSIPAccountExist = doesSIPAccountExist;
                StatefulProxyLogEvent = proxyLog;
			}
			catch(Exception excp)
			{
				logger.Error("Exception StatefulProxyCore (ctor). " + excp.Message);
				throw excp;
			}
		}

        /// <summary>
        /// 
        /// </summary>
        public void GotRequest(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPRequest sipRequest)
		{
            try
            {
                // Used in the proxy monitor messages only, plays no part in request routing.
                string fromUser = (sipRequest.Header.From != null ) ? sipRequest.Header.From.FromURI.User : null;
                string fromURI = (sipRequest.Header.From != null) ? sipRequest.Header.From.FromURI.User + "@" + sipRequest.Header.From.FromURI.Host : "no from header";
                string toUser = (sipRequest.Header.To != null) ? sipRequest.Header.To.ToURI.User : null;
                string summaryStr = "req " + sipRequest.Method + " from=" + fromUser + ", to=" + toUser + ", " + IPSocket.GetSocketString(remoteEndPoint);

                SIPDialogue dialogue = GetDialogue(sipRequest, remoteEndPoint);

                if (dialogue != null && sipRequest.Method != SIPMethodsEnum.ACK)
                {
                    #region Process in dialogue requests.

                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Matching dialogue found for " + sipRequest.Method + " to " + sipRequest.URI.ToString() + " from " + remoteEndPoint + ".",  dialogue.Owner));
                    
                    if (sipRequest.Method == SIPMethodsEnum.BYE)
                    {
                        SIPNonInviteTransaction byeTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol);
                        //logger.Debug("Matching dialogue found for BYE request to " + sipRequest.URI.ToString() + ".");
                        SIPResponse byeResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.Ok, null);
                        byeTransaction.SendFinalResponse(byeResponse);

                        // Let the CallManager know so the forwarded leg of the call can be hung up.
                        m_callManager.CallHungup(dialogue, sipRequest.Header.Reason);
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.INVITE)
                    {
                        UASInviteTransaction reInviteTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, false, null);
                        reInviteTransaction.CDR = null;     // Don't want CDR's on re-INVITEs.
                        m_callManager.ForwardInDialogueRequest(dialogue, reInviteTransaction, localEndPoint, remoteEndPoint);
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.MESSAGE)
                    {
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "MESSAGE for call " + sipRequest.URI.ToString() + ": " + sipRequest.Body + ".", dialogue.Owner));
                        SIPNonInviteTransaction messageTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol);
                        SIPResponse okResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.Ok, null);
                        messageTransaction.SendFinalResponse(okResponse);

                        if(dialogue.Owner != null)
                        {
                            // Break back into the user's dial plan.
                            SIPDialPlan userDialPlan = LoadDialPlan(dialogue.Owner, dialogue.OwnerDomain);
                            if (userDialPlan != null)
                            {
                                userDialPlan.ProcessNonInviteRequest(FireProxyLogEvent, m_callManager, localEndPoint, remoteEndPoint, sipRequest);
                            }
                        }
                    }
                    else
                    {
                        // This is a request on an established call forward through to the other end, no further action required.
                        SIPNonInviteTransaction passThruTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol);
                        m_callManager.ForwardInDialogueRequest(dialogue, passThruTransaction, localEndPoint, remoteEndPoint);
                    }

                    #endregion
                }
                else if (sipRequest.Method == SIPMethodsEnum.NOTIFY)
                {
                    #region NOTIFY request handling.

                    // Check if the notify request is for a sipswitch user.
                    //if (SIPRegistrations.AddressOfRecordExists(sipRequest.URI))
                    if (DoesSIPAccountExist(sipRequest.URI))
                    {
                        //SIPRegistrarRecord registrarRecord = SIPRegistrations.Lookup(new SIPParameterlessURI(sipRequest.URI));
                        //SIPRegistrarRecord registrarRecord = GetSIPAccountBindings(sipRequest.URI.User, sipRequest.URI.Host);
                        List<SIPAddressBinding> bindings = GetSIPAccountBindings(sipRequest.URI.User, sipRequest.URI.Host);

                        if (bindings != null)
                        {
                            //List<SIPAddressBinding> bindings = registrarRecord.GetBindings();

                            foreach (SIPAddressBinding binding in bindings)
                            {
                                SIPURI contactURI = binding.MangledContactURI;
                                IPEndPoint contactEndPoint = contactURI.GetURIEndPoint();

                                // Rather than create a brand new request copy the received one and modify the headers that need to be unique.
                                SIPRequest notifyRequest = sipRequest.Copy();
                                notifyRequest.URI = contactURI;
                                notifyRequest.Header.Contact = SIPContactHeader.ParseContactHeader("sip:" + m_sipTransport.GetTransportContact(null));
                                notifyRequest.Header.To = new SIPToHeader(null, contactURI, null);
                                notifyRequest.Header.CallId = CallProperties.CreateNewCallId();
                                SIPViaHeader viaHeader = new SIPViaHeader(localEndPoint.Address.ToString(), localEndPoint.Port, CallProperties.CreateBranchId());
                                notifyRequest.Header.Via = new SIPViaSet();
                                notifyRequest.Header.Via.PushViaHeader(viaHeader);

                                logger.Debug("Forwarding NOTIFY to switch user binding " + contactURI.ToString() + " at " + IPSocket.GetSocketString(contactEndPoint) + ".");
                                IPEndPoint nextHopSocket = (binding.ProxyEndPoint != null) ? binding.ProxyEndPoint : contactEndPoint;
                                SIPNonInviteTransaction notifyTransaction = m_sipTransport.CreateNonInviteTransaction(notifyRequest, nextHopSocket, m_sipTransport.GetTransportContact(null), protocol);
                                notifyTransaction.SendReliableRequest();
                            }

                            // Send OK response to server.
                            SIPResponse okResponse = SIPTransport.GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.Ok, null, localEndPoint, remoteEndPoint);
                            m_sipTransport.SendResponse(okResponse);
                        }
                        else
                        {
                            // Send Not found response to server.
                            SIPResponse notFoundResponse = SIPTransport.GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.NotFound, null, localEndPoint, remoteEndPoint);
                            m_sipTransport.SendResponse(notFoundResponse);
                        }
                    }
                    else
                    {
                        // Send Not found response to server.
                        SIPResponse okResponse = SIPTransport.GetResponse(sipRequest.Header, SIPResponseStatusCodesEnum.NotFound, null, localEndPoint, remoteEndPoint);
                        m_sipTransport.SendResponse(okResponse);
                    }

                    #endregion
                }
                else if (sipRequest.Method == SIPMethodsEnum.REGISTER)
                {
                    #region REGISTER request handling.

                    if (m_regAgentSocket != null && remoteEndPoint.ToString() == m_regAgentSocket.ToString())
                    {
                        // This is a REGISTER request for the registration agent that should be forwarded out to the 3rd party provider.
                        //SIPViaHeader originalHeader = sipRequest.Header.Via.PopTopViaHeader();
                        //sipRequest.Header.Via.PushViaHeader(new SIPViaHeader(localEndPoint, originalHeader.Branch));
                        sipRequest.Header.Via.PushViaHeader(new SIPViaHeader(localEndPoint, CallProperties.CreateBranchId()));

                        // The registration agent has indicated where it wants the REGISTER request sent to by adding a Route header.
                        // Remove the header in case it confuses the SIP Registrar the REGISTER is being sent to.
                        SIPRoute registrarRoute = sipRequest.Header.Routes.PopRoute();
                        sipRequest.Header.Routes = null;
                        m_sipTransport.SendRequest(registrarRoute.GetEndPoint(), SIPProtocolsEnum.UDP, sipRequest);
                    }
                    else if (m_registrarSocket != null)
                    {
                        // If the SIP Registrar is in the same process add a Via Header on for the received end point.
                        // This will allow the proxy and registrar to be separated easily if the need arises.
                        sipRequest.Header.Via.TopViaHeader.ViaParameters.Set(m_proxyViaParameterName, localEndPoint.ToString());
                        sipRequest.Header.Via.PushViaHeader( new SIPViaHeader(localEndPoint, CallProperties.CreateBranchId()));
                        m_sipTransport.SendRequest(m_registrarSocket, SIPProtocolsEnum.UDP, sipRequest);
                        //m_registrar.AddRegisterRequest(localEndPoint, remoteEndPoint, protocol, sipRequest);
                    }
                    else
                    {
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Warn, "MethodNotAllowed response for " + sipRequest.Method + " from " + fromUser + " socket " + IPSocket.GetSocketString(remoteEndPoint) + ".", null));
                        SIPResponse notAllowedResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.MethodNotAllowed, null);
                        m_sipTransport.SendResponseFrom(localEndPoint, notAllowedResponse);
                    }

                    #endregion
                }
                else if (sipRequest.Method == SIPMethodsEnum.CANCEL)
                {
                    #region CANCEL request handling.

                    UASInviteTransaction inviteTransaction = (UASInviteTransaction)m_sipTransport.GetTransaction(SIPTransaction.GetRequestTransactionId(sipRequest.Header.Via.TopViaHeader.Branch, SIPMethodsEnum.INVITE));

                    if (inviteTransaction != null)
                    {
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Cancelling call for " + sipRequest.URI.ToString() + ".", fromUser));
                        SIPCancelTransaction cancelTransaction = m_sipTransport.CreateCancelTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, inviteTransaction);
                        cancelTransaction.GotRequest(localEndPoint, remoteEndPoint, sipRequest);
                    }
                    else
                    {
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "No matching transaction was found for CANCEL to " + sipRequest.URI.ToString() + ".", fromUser));
                        SIPResponse noCallLegResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null);
                        m_sipTransport.SendResponseFrom(localEndPoint, noCallLegResponse);
                    }

                    #endregion
                }
                else if (sipRequest.Method == SIPMethodsEnum.BYE)
                {
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "No dialogue matched for BYE to " + sipRequest.URI.ToString() + ".", fromUser));
                    SIPResponse noCallLegResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null);
                    m_sipTransport.SendResponseFrom(localEndPoint, noCallLegResponse);                    
                }
                else if (sipRequest.Method == SIPMethodsEnum.REFER)
                {
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "No dialogue matched for REFER to " + sipRequest.URI.ToString() + ".", fromUser));
                    SIPResponse noCallLegResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null);
                    m_sipTransport.SendResponseFrom(localEndPoint, noCallLegResponse);
                }
                else if (sipRequest.Method == SIPMethodsEnum.ACK)
                {
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "No transaction matched for ACK for " + sipRequest.URI.ToString() + ".", fromUser));
                }
                else if (sipRequest.Method == SIPMethodsEnum.INVITE)
                {
                    #region INVITE request processing.

                    string callUser = sipRequest.URI.User;
                    string domain = sipRequest.URI.Host;
                    string domainUser = callUser + "@" + sipRequest.URI.Host;
                    string fromUsername = sipRequest.Header.From.FromUserField.URI.User;
                    string fromUserRealm = sipRequest.Header.From.FromUserField.URI.Host;
                    string canonicalFromDomain = m_sipDomains.GetDomain(fromUserRealm);

                    // If mangling for private IP addresses is required adjust the contact address if present and if an INVITE request the SDP address.
                    if (m_manglePrivateAddresses)
                    {
                        SIPPacketMangler.MangleSIPRequest(ProxyServerTypesEnum.StatefulProxy, sipRequest, fromUser, FireProxyLogEvent);
                    }

                    if (m_sipDomains.GetDomain(fromUserRealm) != null)
                    {
                        #region Call identified as outgoing call for proxy for sipswitch user.

                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Outgoing call to " + sipRequest.URI.ToString() + " for " + fromUsername + "@" + canonicalFromDomain + ".", fromUsername));

                        SIPDialPlan userDialPlan = null;
                        if (fromUsername != null && fromUsername.Trim().Length > 0)
                        {
                            userDialPlan = LoadDialPlan(fromUsername, canonicalFromDomain);
                        }

                        // This call is from a purported sipswitch user attempting to place an outgoing call.
                        if (userDialPlan != null)
                        {
                            UASInviteTransaction clientTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, true, canonicalFromDomain);
                            if (clientTransaction.TransactionCreationError == SIPValidationError.None)
                            {
                                clientTransaction.CDR.Owner = userDialPlan.Username;
                                clientTransaction.UASInviteTransactionRequestAuthenticate += new SIPTransactionAuthenticationRequiredDelegate(AuthenticateClient);
                                clientTransaction.GotRequest(localEndPoint, remoteEndPoint, sipRequest);
                            }
                            else
                            {
                                SIPResponse badReqResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.BadRequest, clientTransaction.TransactionCreationError.ToString());
                                m_sipTransport.SendResponseFrom(localEndPoint, remoteEndPoint, protocol, badReqResponse);
                            }
                        }
                        else
                        {
                            // Send server error response.
                            //logger.Debug("Error loading user configuration for username and call " + sipRequest.URI.ToString() + ".");
                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Error, "Error loading user configuration for username and call " + sipRequest.URI.ToString() + ".", fromUsername));

                            UASInviteTransaction inviteTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, false, null);
                            if (inviteTransaction.TransactionCreationError == SIPValidationError.None)
                            {
                                SIPResponse forbiddenResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.Forbidden, "No user " + fromUser);
                                inviteTransaction.SendFinalResponse(forbiddenResponse);
                            }
                            else
                            {
                                SIPResponse badReqResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.BadRequest, inviteTransaction.TransactionCreationError.ToString());
                                m_sipTransport.SendResponseFrom(localEndPoint, remoteEndPoint, protocol, badReqResponse);
                            }
                        }

                        #endregion
                    }
                    else
                    {
                        // SIP URIs for incoming calls can now be handled as <string>.username, this allows different incoming contacts to be processed by a single sipswitch account.
                        string callUserHost = (callUser != null && callUser.IndexOf('.') != -1) ? callUser.Substring(callUser.LastIndexOf('.') + 1) : callUser;
                        SIPURI addressOfRecordURI = SIPURI.ParseSIPURI("sip:" + callUserHost + "@" + domain);
                        SIPParameterlessURI addressOfRecord = new SIPParameterlessURI(addressOfRecordURI);
                        string canonicalToDomain = m_sipDomains.GetDomain(domain);

                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Incoming call to " + sipRequest.URI.ToString() + " canonincal domain is " + canonicalToDomain + ".", null));

                        if (canonicalToDomain != null)
                        {
                            SIPDialPlan userDialPlan = null;
                            if (callUserHost != null && callUserHost.Trim().Length > 0)
                            {
                                userDialPlan = LoadDialPlan(callUserHost, canonicalToDomain);
                            }

                            if (userDialPlan == null)
                            {
                                // Check whether this URI is a sipswitch extension.
                                string extensionOwner = (GetExtensionOwner != null) ? GetExtensionOwner(sipRequest.URI.User, canonicalToDomain) : null;
                                if (extensionOwner != null)
                                {
                                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Incoming call on extension for " + extensionOwner  + " to " + sipRequest.URI.ToString() + " from " + fromURI + ".", extensionOwner));
                                    userDialPlan = (LoadDialPlan != null) ? LoadDialPlan(extensionOwner, canonicalToDomain) : null;
                                }
                            }
                            
                            if (userDialPlan != null)
                            {
                                #region Incoming calls to a sipswitch user send to all current contacts or route via dialplan entry.

                                //logger.Debug("INVITE identified as incoming call for sipswitch user, checking whether dialplan should be used for " + sipRequest.URI.ToString() + ".");

                                // Dial plan will only be used for incoming calls if there is an exact match on a dial plan entry.
                                if (userDialPlan.IsScript || userDialPlan.GetDialPlanExactMatch(localEndPoint, remoteEndPoint, sipRequest) != null)
                                {
                                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Using dial plan for incoming call to " + sipRequest.URI.ToString() + ".", userDialPlan.Username));
                                    UASInviteTransaction clientTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, false, canonicalToDomain);
                                    if (clientTransaction.TransactionCreationError == SIPValidationError.None)
                                    {
                                        clientTransaction.CDR.Owner = userDialPlan.Username;
                                        if (userDialPlan.IsScript)
                                        {
                                            userDialPlan.ProcessRequest(FireProxyLogEvent, m_createBridgeDelegate, localEndPoint, remoteEndPoint, clientTransaction, null, canonicalToDomain);
                                        }
                                        else
                                        {
                                            SIPDialPlanCommand matchedCommand = userDialPlan.GetDialPlanExactMatch(m_sipTransport.GetTransportContact(null), remoteEndPoint, sipRequest);
                                            userDialPlan.ProcessDialPlanRequest(FireProxyLogEvent, m_createBridgeDelegate, matchedCommand, localEndPoint, remoteEndPoint, clientTransaction, m_manglePrivateAddresses, null, canonicalToDomain);
                                        }
                                    }
                                    else
                                    {
                                        SIPResponse badReqResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.BadRequest, clientTransaction.TransactionCreationError.ToString());
                                        m_sipTransport.SendResponseFrom(localEndPoint, remoteEndPoint, protocol, badReqResponse);
                                    }
                                }
                                else if (DoesSIPAccountExist(addressOfRecordURI))
                                {
                                    //logger.Debug("No entry in dial plan for the URI on an incoming call, falling back to using user's registered contacts.");
                                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Retrieving contacts for " + domainUser + ".", callUserHost));

                                    SIPCallResolver callResolver = new SIPCallResolver(m_sipTransport, addressOfRecordURI.User, null, GetSIPAccountBindings, m_sipDomains);
                                    List<SIPCallDescriptor> localUserSwitchCalls = callResolver.GetCallListForLocalUser(addressOfRecordURI.User, canonicalToDomain, sipRequest.Header.From.ToString());

                                    if (localUserSwitchCalls.Count > 0)
                                    {
                                        UASInviteTransaction clientTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, false, canonicalToDomain);
                                        if (clientTransaction.TransactionCreationError == SIPValidationError.None)
                                        {
                                            clientTransaction.CDR.Owner = userDialPlan.Username;
                                            SwitchCallMulti switchCallMulti = new SwitchCallMulti(m_sipTransport, new ProxyLogDelegate(FireProxyLogEvent), m_createBridgeDelegate, clientTransaction, m_manglePrivateAddresses, callUser, canonicalToDomain, null, null);
                                            switchCallMulti.Start(localUserSwitchCalls);
                                        }
                                        else
                                        {
                                            SIPResponse badReqResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.BadRequest, clientTransaction.TransactionCreationError.ToString());
                                            m_sipTransport.SendResponseFrom(localEndPoint, remoteEndPoint, protocol, badReqResponse);
                                        }
                                    }
                                    else
                                    {
                                        // Send unavailable response.
                                        //logger.Debug("No current contacts found for " + addressOfRecord.ToString() + ", returning not available.");
                                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "No current contacts found for " + addressOfRecord.ToString() + ", returning not available.", addressOfRecord.User));

                                        SIPResponse busyResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.TemporarilyNotAvailable, "No current contacts registered");
                                        UASInviteTransaction inviteTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, false, null);
                                        if (inviteTransaction.TransactionCreationError == SIPValidationError.None)
                                        {
                                            inviteTransaction.SendFinalResponse(busyResponse);
                                        }
                                        else
                                        {
                                            m_sipTransport.SendResponseFrom(localEndPoint, busyResponse);
                                        }
                                    }
                                }
                                else
                                {
                                    // This is a sipswitch user but they are not logged in and do not have incoming dial plan option chosen, return unavailable.
                                    //logger.Debug("No registrations or dial plan entry for incoming call and " + addressOfRecord.ToString() + ", returning temporarily not available.");
                                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "No registrations or dial plan entry for incoming call and " + addressOfRecord.ToString() + ", returning temporarily not available.", addressOfRecord.User));

                                    SIPResponse unavailResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.TemporarilyNotAvailable, null);
                                    UASInviteTransaction inviteTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, false, null);
                                    if (inviteTransaction.TransactionCreationError == SIPValidationError.None)
                                    {
                                        inviteTransaction.SendFinalResponse(unavailResponse);
                                    }
                                    else
                                    {
                                        m_sipTransport.SendResponseFrom(localEndPoint, unavailResponse);
                                    }
                                }

                                #endregion
                            }
                            else
                            {
                                #region Return user not found.

                                FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "User not found for " + sipRequest.URI.ToString() + ", returning not found.", null));

                                SIPResponse notFoundResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.NotFound, null);
                                UASInviteTransaction inviteTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, false, null);
                                if (inviteTransaction.TransactionCreationError == SIPValidationError.None)
                                {
                                    inviteTransaction.SendFinalResponse(notFoundResponse);
                                }
                                else
                                {
                                    m_sipTransport.SendResponseFrom(localEndPoint, notFoundResponse);
                                }

                                #endregion
                            }
                        }
                        else
                        {   
                            #region Return not found for non-service domain.

                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Domain not serviced " + sipRequest.URI.ToString() + ", returning not found.", null));

                            SIPResponse notFoundResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.NotFound, "Domain not serviced");
                            UASInviteTransaction inviteTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localEndPoint, protocol, false, null);
                            if (inviteTransaction.TransactionCreationError == SIPValidationError.None)
                            {
                                inviteTransaction.SendFinalResponse(notFoundResponse);
                            }
                            else
                            {
                                m_sipTransport.SendResponseFrom(localEndPoint, notFoundResponse);
                            }

                            #endregion
                        }
                    }

                    #endregion
                }
                else
                {
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.UnrecognisedMessage, "MethodNotAllowed response for " + sipRequest.Method + " from " + fromUser + " socket " + IPSocket.GetSocketString(remoteEndPoint) + ".", null));
                    SIPResponse notAllowedResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.MethodNotAllowed, null);
                    m_sipTransport.SendResponseFrom(localEndPoint, notAllowedResponse);
                }
            }
            catch (Exception excp)
            {
                string reqExcpError = "Exception StatefulProxyCore GotRequest (" + remoteEndPoint + "). " + excp.Message;
                logger.Error(reqExcpError);
                
                ProxyMonitorEvent reqExcpEvent = new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Error, reqExcpError, sipRequest, null, localEndPoint, remoteEndPoint, SIPCallDirection.In);
                FireProxyLogEvent(reqExcpEvent);

                throw excp;
            }
		}

        public void GotResponse(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPResponse sipResponse)
        {
            try
            {
                // The 3 fields below are used in the proxy monitor messages only, plays no part in response processing.
                //string fromUser = (sipResponse.Header.From != null) ? sipResponse.Header.From.FromURI.User : null;
                //string toUser = (sipResponse.Header.To != null) ? sipResponse.Header.To.ToURI.User : null;
                //string summaryStr = "resp " + sipResponse.Header.CSeqMethod + " " + sipResponse.StatusCode + " " + sipResponse.Status + ", from=" + fromUser + ", to=" + toUser + ", fromsock=" + IPSocket.GetSocketString(remoteEndPoint);

                if (sipResponse.Header.Via != null && sipResponse.Header.Via.TopViaHeader != null)
                {
                    SIPViaHeader proxyVia = sipResponse.Header.Via.PopTopViaHeader();

                   /* if (remoteEndPoint.ToString() != m_registrarSocket.ToString() && sipResponse.Header.CSeqMethod == SIPMethodsEnum.REGISTER)
                    {
                        // A REGISTER response that has not come from the SIP Registrar socket is for the Registration Agent. In that case we need to add back on
                        // the Via header that was removed when the original REGISTER request was passed through from the Agent.
                        sipResponse.Header.Via.PushViaHeader(new SIPViaHeader(m_regAgentSocket, proxyVia.Branch));

                        m_sipTransport.SendResponse(sipResponse);
                    }
                    else */
                    if (m_sipTransport.IsLocalSIPSocket(proxyVia.ContactAddress))
                    //else if(proxyVia.Host == m_sipTransport.GetTransportContact(null).Address.ToString() && proxyVia.Port == m_sipTransport.GetTransportContact(null).Port)
                    {
                        if (sipResponse.Header.Via.Length > 0)
                        {
                            if (sipResponse.Header.CSeqMethod == SIPMethodsEnum.REGISTER)
                            {
                                // Remove proxy parameter from via.
                                sipResponse.Header.Via.TopViaHeader.ViaParameters.Remove(m_proxyViaParameterName);
                            }

                            m_sipTransport.SendResponse(sipResponse);
                        }
                        else
                        {
                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Warn, "An SIP repsonse not belonging to a transaction was received from " + remoteEndPoint + ".", null));
                        }
                    }
                    else
                    {
                        // Response not for this proxy, ignoring.
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Warn, "Dropping SIP response from " + remoteEndPoint + ", because the top Via header was not for this proxy, top header was " + proxyVia.ToString() + ".", null));
                    }
                }
                else
                {
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Error, "SIP Response from " + remoteEndPoint + " received with no Via headers.", null));
                }
            }
            catch (Exception excp)
            {
                FireProxyLogEvent( new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Error, "Exception StatefulProxyCore GotResponse (" + remoteEndPoint + "). " + excp.Message + "\n" + sipResponse.ToString(), null));
                throw excp;
            }
        }

		private SIPResponse GetProxyResponse(SIPHeader requestHeader, SIPResponseStatusCodesEnum responseCode, string reasonPhrase)
		{	
			try
			{
				SIPResponse proxyResponse = new SIPResponse(responseCode);

                if (reasonPhrase != null && reasonPhrase.Trim().Length > 0)
                {
                    proxyResponse.ReasonPhrase = reasonPhrase;
                }
			
				proxyResponse.Header = new SIPHeader(requestHeader.From, requestHeader.To, requestHeader.CSeq, requestHeader.CallId);
				proxyResponse.Header.CSeqMethod = requestHeader.CSeqMethod;
				proxyResponse.Header.Via = requestHeader.Via;
				proxyResponse.Header.UserAgent = SIPPROXY_USERAGENT;
				proxyResponse.Header.MaxForwards = Int32.MinValue;

				return proxyResponse;
			}
			catch(Exception excp)
			{
				logger.Error("Exception GetProxyResponse. " + excp.Message);
				throw excp;
			}
		}

        /// <summary>
        /// Authenticates an INVITE request from a client into the SIP switch. If successful the call is sent to the user's dialplan for processing.
        /// </summary>
        private void AuthenticateClient(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction transaction, SIPAuthenticationHeader reqAuthHeader)
        {            
            try
            {
                SIPRequest sipRequest = transaction.TransactionRequest;
                string authUsername = reqAuthHeader.AuthRequest.Username;
                string realm = reqAuthHeader.AuthRequest.Realm;
                string canonicalFromDomain = m_sipDomains.GetDomain(realm); //SIPRegistrations.GetDomain(realm);
                SIPAccount sipAccount = LoadSIPAccount(authUsername, canonicalFromDomain);

                if (sipAccount != null)
                {
                    string requestNonce = reqAuthHeader.AuthRequest.Nonce;
                    string uri = reqAuthHeader.AuthRequest.URI;
                    string response = reqAuthHeader.AuthRequest.Response;

                    if (canonicalFromDomain != null && realm != canonicalFromDomain)
                    {
                        logger.Debug("The request realm " + realm + " did not match " + canonicalFromDomain + " Forbidden response being sent for INVITE from " + authUsername + " socket " + IPSocket.GetSocketString(remoteEndPoint) + ".");

                        SIPResponse forbiddenResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.Forbidden, "Realms did not match");
                        transaction.SendFinalResponse(forbiddenResponse);
                    }
                    else
                    {
                        AuthorizationRequest checkAuthReq = new AuthorizationRequest(canonicalFromDomain, authUsername, sipAccount.SIPPassword, uri, requestNonce, SIPMethodsEnum.INVITE.ToString());
                        string digest = checkAuthReq.Digest;

                        if (digest != response)
                        {
                            logger.Warn("Authentication token check failed for realm=" + realm + ", username=" + authUsername + ", uri=" + uri + ", nonce=" + requestNonce + ", method=" + sipRequest.Method + ".");

                            // 407 Response with a fresh nonce needs to be sent.
                            SIPResponse authReqdResponse = ((UASInviteTransaction)transaction).GetAuthReqdResponse(sipRequest.Header, canonicalFromDomain, Crypto.GetRandomInt().ToString());
                            transaction.SendFinalResponse(authReqdResponse);
                        }
                        else
                        {
                            logger.Debug("UAC call for " + authUsername + " and " + uri.ToString() + " authenticated.");

                            #region Parse out some information from the INVITE body to help in identifying some User Agents to allow a pretty picture on control panel.

                            // Updates the SDP Owner string for a registered contact to help in identifying the User Agent.
                            if (Regex.Match(sipRequest.Body, @"(\r|\n)o=(?<owner>.+?)(\r|\n)", RegexOptions.Singleline).Success)
                            {
                                string domainUser = authUsername + "@" + realm;
                                string sdpOwner = Regex.Match(sipRequest.Body, @"(\r|\n)o=(?<owner>.+?)(\r|\n)", RegexOptions.Singleline).Result("${owner}");
                                //SIPRegistrations.UpdateContactSDPOwner(new SIPParameterlessURI(sipRequest.Header.From.FromURI), sipRequest.Header.Contact[0], sdpOwner);
                            }

                            #endregion

                            SIPDialPlan dialPlan = LoadDialPlan(authUsername, canonicalFromDomain);
                            dialPlan.ProcessRequest(FireProxyLogEvent, m_createBridgeDelegate, localEndPoint, remoteEndPoint, (UASInviteTransaction)transaction, canonicalFromDomain, null);
                        }
                    }
                }
                else
                {
                    logger.Debug("No configuration found for " + authUsername + " returning 501 ServerError to " + IPSocket.GetSocketString(remoteEndPoint) + ".");
                    SIPResponse serverErrorResponse = GetProxyResponse(sipRequest.Header, SIPResponseStatusCodesEnum.InternalServerError, "Dialplan could not be located");
                    transaction.SendFinalResponse(serverErrorResponse);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception AuthenticateClient. " + excp.Message);
            }
        }

        /// <summary>
        /// Attempts to locate a dialogue for an in-dialogue transaction.
        /// </summary>
        /// <param name="transaction"></param>
        /// <returns></returns>
        private SIPDialogue GetDialogue(SIPRequest sipRequest, IPEndPoint remoteEndPoint)
        {
            try
            {
                string callId = sipRequest.Header.CallId;
                string localTag = sipRequest.Header.To.ToTag;
                string remoteTag = sipRequest.Header.From.FromTag;

                return m_callManager.GetDialogue(callId, localTag, remoteTag, remoteEndPoint);
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetDialogue. " + excp);
                return null;
            }
        }

        private void FireProxyLogEvent(ProxyMonitorEvent monitorEvent)
        {           
            if (StatefulProxyLogEvent != null)
            {
                try
                {
                    StatefulProxyLogEvent(monitorEvent);
                }
                catch (Exception excp)
                {
                    logger.Error("Exception FireProxyLogEvent StatefulProxyCore. " + excp.Message);
                }
            }
        }

        #region Unit testing.

		#if UNITTEST
	
		[TestFixture]
		public class StatefulProxyCoreUnitTest
		{           
            [TestFixtureSetUp]
			public void Init()
			{
			}
	
			[TestFixtureTearDown]
			public void Dispose()
			{			
				
			}

			[Test]
			public void SampleTest()
			{
				Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);
				Assert.IsTrue(true, "True was false.");
			}

            [Test]
            public void CreateStatefulProxyTest()
            {
                SIPTransactionEngine transactionEngine = new SIPTransactionEngine();
                SIPTransport sipTransport = new SIPTransport(transactionEngine, new IPEndPoint(IPAddress.Loopback, 3000), false, false);
                StatefulProxyCore statefulProxyCore = new StatefulProxyCore(sipTransport, null, null, false, null);
                sipTransport.Shutdown();
            }

            [Test]
            public void B2BOptionsStatefulProxyTest()
            {
                SIPTransactionEngine transactionEngine1 = new SIPTransactionEngine();
                SIPTransport sipTransport1 = new SIPTransport(transactionEngine1, true, false);
                sipTransport1.AddSIPChannel(new IPEndPoint(IPAddress.Loopback, 3000));
                StatefulProxyCore statefulProxyCore1 = new StatefulProxyCore(sipTransport1, null, null, false, null);

                SIPTransactionEngine transactionEngine2 = new SIPTransactionEngine();
                SIPTransport sipTransport2 = new SIPTransport(transactionEngine2, true, false);
                sipTransport2.AddSIPChannel(new IPEndPoint(IPAddress.Loopback, 3001));
                StatefulProxyCore statefulProxyCore2 = new StatefulProxyCore(sipTransport2, null, null, false, null);

                sipTransport1.SIPRequestOutTraceEvent += new SIPTransportSIPRequestOutTraceDelegate(sipTransport1_SIPRequestOutTraceEvent);
                sipTransport1.SIPResponseInTraceEvent += new SIPTransportSIPResponseInTraceDelegate(sipTransport1_SIPResponseInTraceEvent);
                sipTransport2.SIPRequestInTraceEvent += new SIPTransportSIPRequestInTraceDelegate(sipTransport2_SIPRequestInTraceEvent);
                statefulProxyCore1.StatefulProxyLogEvent += new ProxyLogDelegate(statefulProxyCore1_StatefulProxyLogEvent);
                statefulProxyCore2.StatefulProxyLogEvent += new ProxyLogDelegate(statefulProxyCore2_StatefulProxyLogEvent);

                SIPRequest optionsRequest = GetOptionsRequest(SIPURI.ParseSIPURI("sip:127.0.0.1:3001"), 1, sipTransport1.GetDefaultTransportContact(SIPProtocolsEnum.UDP));
                sipTransport1.SendRequest(optionsRequest, SIPProtocolsEnum.UDP);

                Thread.Sleep(200);

                // Check the NUnit Console.Out to make sure there are SIP requests and responses being displayed.

                sipTransport1.Shutdown();
                sipTransport2.Shutdown();
            }

            [Test]
            public void B2BInviteStatefulProxyTest()
            {
                SIPTransactionEngine transactionEngine1 = new SIPTransactionEngine();
                SIPTransport sipTransport1 = new SIPTransport(transactionEngine1, true, false);
                IPEndPoint sipTransport1EndPoint = new IPEndPoint(IPAddress.Loopback, 3000);
                sipTransport1.AddSIPChannel(sipTransport1EndPoint);
                StatefulProxyCore statefulProxyCore1 = new StatefulProxyCore(sipTransport1, null, null, false, null);

                SIPTransactionEngine transactionEngine2 = new SIPTransactionEngine();
                SIPTransport sipTransport2 = new SIPTransport(transactionEngine2, true, false);
                IPEndPoint sipTransport2EndPoint = new IPEndPoint(IPAddress.Loopback, 3001);
                sipTransport2.AddSIPChannel(sipTransport2EndPoint);
                StatefulProxyCore statefulProxyCore2 = new StatefulProxyCore(sipTransport2, null, null, false, null);
                statefulProxyCore2.GetCanonicalDomain += new GetCanonicalDomainDelegate(statefulProxyCore2_GetCanonicalDomain);

                sipTransport1.SIPRequestOutTraceEvent += new SIPTransportSIPRequestOutTraceDelegate(sipTransport1_SIPRequestOutTraceEvent);
                sipTransport1.SIPResponseInTraceEvent += new SIPTransportSIPResponseInTraceDelegate(sipTransport1_SIPResponseInTraceEvent);
                sipTransport2.SIPRequestInTraceEvent += new SIPTransportSIPRequestInTraceDelegate(sipTransport2_SIPRequestInTraceEvent);
                sipTransport2.SIPResponseOutTraceEvent += new SIPTransportSIPResponseOutTraceDelegate(sipTransport2_SIPResponseOutTraceEvent);
                statefulProxyCore1.StatefulProxyLogEvent += new ProxyLogDelegate(statefulProxyCore1_StatefulProxyLogEvent);
                statefulProxyCore2.StatefulProxyLogEvent += new ProxyLogDelegate(statefulProxyCore2_StatefulProxyLogEvent);

                SIPRequest inviteRequest = GetInviteRequest(sipTransport1EndPoint, null, sipTransport2EndPoint);
                sipTransport1.SendRequest(inviteRequest, SIPProtocolsEnum.UDP);

                Thread.Sleep(200);

                // Check the NUnit Console.Out to make sure there are SIP requests and responses being displayed.

                sipTransport1.Shutdown();
                sipTransport2.Shutdown();
            }

            [Test]
            public void B2BInviteTransactionStatefulProxyTest()
            {
                SIPTransactionEngine transactionEngine1 = new SIPTransactionEngine();
                SIPTransport sipTransport1 = new SIPTransport(transactionEngine1, true, false);
                IPEndPoint sipTransport1EndPoint = new IPEndPoint(IPAddress.Loopback, 3000);
                sipTransport1.AddSIPChannel(sipTransport1EndPoint);
                StatefulProxyCore statefulProxyCore1 = new StatefulProxyCore(sipTransport1, null, null, false, null);

                SIPTransactionEngine transactionEngine2 = new SIPTransactionEngine();
                SIPTransport sipTransport2 = new SIPTransport(transactionEngine2, true, false);
                IPEndPoint sipTransport2EndPoint = new IPEndPoint(IPAddress.Loopback, 3001);
                sipTransport2.AddSIPChannel(sipTransport2EndPoint);
                StatefulProxyCore statefulProxyCore2 = new StatefulProxyCore(sipTransport2, null, null, false, null);
                statefulProxyCore2.GetCanonicalDomain += new GetCanonicalDomainDelegate(statefulProxyCore2_GetCanonicalDomain);

                sipTransport1.SIPRequestOutTraceEvent += new SIPTransportSIPRequestOutTraceDelegate(sipTransport1_SIPRequestOutTraceEvent);
                sipTransport1.SIPResponseInTraceEvent += new SIPTransportSIPResponseInTraceDelegate(sipTransport1_SIPResponseInTraceEvent);
                sipTransport2.SIPRequestInTraceEvent += new SIPTransportSIPRequestInTraceDelegate(sipTransport2_SIPRequestInTraceEvent);
                sipTransport2.SIPResponseOutTraceEvent += new SIPTransportSIPResponseOutTraceDelegate(sipTransport2_SIPResponseOutTraceEvent);
                statefulProxyCore1.StatefulProxyLogEvent += new ProxyLogDelegate(statefulProxyCore1_StatefulProxyLogEvent);
                statefulProxyCore2.StatefulProxyLogEvent += new ProxyLogDelegate(statefulProxyCore2_StatefulProxyLogEvent);

                SIPRequest inviteRequest = GetInviteRequest(sipTransport1EndPoint, null, sipTransport2EndPoint);
                UACInviteTransaction uacInvite = sipTransport1.CreateUACTransaction(inviteRequest, sipTransport2EndPoint, sipTransport1EndPoint, SIPProtocolsEnum.UDP);
                uacInvite.SendInviteRequest(sipTransport2EndPoint, inviteRequest);

                Thread.Sleep(200);

                // Check the NUnit Console.Out to make sure there are SIP requests and responses being displayed.

                sipTransport1.Shutdown();
                sipTransport2.Shutdown();
            }


            [Test]
            public void B2BInviteTransactionUserFoundStatefulProxyTest()
            {
                SIPTransactionEngine transactionEngine1 = new SIPTransactionEngine();
                SIPTransport sipTransport1 = new SIPTransport(transactionEngine1, true, false);
                IPEndPoint sipTransport1EndPoint = new IPEndPoint(IPAddress.Loopback, 3000);
                sipTransport1.AddSIPChannel(sipTransport1EndPoint);
                StatefulProxyCore statefulProxyCore1 = new StatefulProxyCore(sipTransport1, null, null, false, null);

                SIPTransactionEngine transactionEngine2 = new SIPTransactionEngine();
                SIPTransport sipTransport2 = new SIPTransport(transactionEngine2, true, false);
                IPEndPoint sipTransport2EndPoint = new IPEndPoint(IPAddress.Loopback, 3001);
                sipTransport2.AddSIPChannel(sipTransport2EndPoint);
                StatefulProxyCore statefulProxyCore2 = new StatefulProxyCore(sipTransport2, null, null, false, null);
                statefulProxyCore2.GetCanonicalDomain += new GetCanonicalDomainDelegate(statefulProxyCore2_GetCanonicalDomain);
                statefulProxyCore2.GetExtensionOwner += new GetExtensionOwnerDelegate(statefulProxyCore2_GetExtensionOwner);

                sipTransport1.SIPRequestOutTraceEvent += new SIPTransportSIPRequestOutTraceDelegate(sipTransport1_SIPRequestOutTraceEvent);
                sipTransport1.SIPResponseInTraceEvent += new SIPTransportSIPResponseInTraceDelegate(sipTransport1_SIPResponseInTraceEvent);
                sipTransport2.SIPRequestInTraceEvent += new SIPTransportSIPRequestInTraceDelegate(sipTransport2_SIPRequestInTraceEvent);
                sipTransport2.SIPResponseOutTraceEvent += new SIPTransportSIPResponseOutTraceDelegate(sipTransport2_SIPResponseOutTraceEvent);
                statefulProxyCore1.StatefulProxyLogEvent += new ProxyLogDelegate(statefulProxyCore1_StatefulProxyLogEvent);
                statefulProxyCore2.StatefulProxyLogEvent += new ProxyLogDelegate(statefulProxyCore2_StatefulProxyLogEvent);
                statefulProxyCore2.LoadDialPlan += new LoadDialPlanDelegate(statefulProxyCore2_LoadDialPlan);

                SIPRequest inviteRequest = GetInviteRequest(sipTransport1EndPoint, null, sipTransport2EndPoint);
                UACInviteTransaction uacInvite = sipTransport1.CreateUACTransaction(inviteRequest, sipTransport2EndPoint, sipTransport1EndPoint, SIPProtocolsEnum.UDP);
                uacInvite.SendInviteRequest(sipTransport2EndPoint, inviteRequest);

                Thread.Sleep(1000);

                // Check the NUnit Console.Out to make sure there are SIP requests and responses being displayed.

                sipTransport1.Shutdown();
                sipTransport2.Shutdown();
            }

            SIPDialPlan statefulProxyCore2_LoadDialPlan(string sipAccountUsername, string sipAccountDomain)
            {
                return new SIPDialPlan(null, null, null, null, null, null, null);
            }

            string statefulProxyCore2_GetExtensionOwner(string user, string domain)
            {
                return "joe.bloggs";
            }

            string statefulProxyCore2_GetCanonicalDomain(string domain)
            {
                return domain;
            }

            void statefulProxyCore2_StatefulProxyLogEvent(ProxyMonitorEvent logEvent)
            {
                Console.WriteLine("StateFulProxy2-" + logEvent.EventType + ": " + logEvent.Message);
            }

            void statefulProxyCore1_StatefulProxyLogEvent(ProxyMonitorEvent logEvent)
            {
                Console.WriteLine("StateFulProxy1-" + logEvent.EventType + ": " + logEvent.Message);
            }

            void sipTransport2_SIPResponseOutTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPResponse sipResponse)
            {
                Console.WriteLine("Response Sent (" + protocol + "): " + localEndPoint + "<-" + toEndPoint + "\r\n" + sipResponse.ToString());
            }

            void sipTransport1_SIPResponseInTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPResponse sipResponse)
            {
                Console.WriteLine("Response Received (" + protocol + "): " + localEndPoint + "<-" + fromEndPoint + "\r\n" + sipResponse.ToString());
            }

            void sipTransport1_SIPRequestOutTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint toEndPoint, SIPRequest sipRequest)
            {
                Console.WriteLine("Request Sent (" + protocol + "): " + localEndPoint + "<-" + toEndPoint + "\r\n" + sipRequest.ToString());
            }

            void sipTransport2_SIPRequestInTraceEvent(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint fromEndPoint, SIPRequest sipRequest)
            {
                Console.WriteLine("Request Received (" + protocol + "): " + localEndPoint + "<-" + fromEndPoint + "\r\n" + sipRequest.ToString());
            }

            private SIPRequest GetOptionsRequest(SIPURI serverURI, int cseq, IPEndPoint contact)
            {
                SIPRequest optionsRequest = new SIPRequest(SIPMethodsEnum.OPTIONS, serverURI);

                SIPFromHeader fromHeader = new SIPFromHeader(null, SIPURI.ParseSIPURI("sip:" + contact.ToString()), null);
                SIPToHeader toHeader = new SIPToHeader(null, serverURI, null);

                string callId = CallProperties.CreateNewCallId();
                string branchId = CallProperties.CreateBranchId();

                SIPHeader header = new SIPHeader(fromHeader, toHeader, cseq, callId);
                header.CSeqMethod = SIPMethodsEnum.OPTIONS;
                header.MaxForwards = 0;

                SIPViaHeader viaHeader = new SIPViaHeader(contact.Address.ToString(), contact.Port, branchId);
                header.Via.PushViaHeader(viaHeader);

                optionsRequest.Header = header;

                return optionsRequest;
            }

            private SIPRequest GetInviteRequest(IPEndPoint localContact, string inviteBody, IPEndPoint dstEndPoint)
            {
                SIPRequest inviteRequest = new SIPRequest(SIPMethodsEnum.INVITE, SIPURI.ParseSIPURI("sip:" + dstEndPoint));

                SIPHeader inviteHeader = new SIPHeader(SIPFromHeader.ParseFromHeader("<sip:" + localContact + ">"), SIPToHeader.ParseToHeader("<sip:" + dstEndPoint + ">"), 1, CallProperties.CreateNewCallId());
                inviteHeader.From.FromTag = CallProperties.CreateNewTag();
                inviteHeader.Contact = SIPContactHeader.ParseContactHeader("sip:" + localContact);
                inviteHeader.CSeqMethod = SIPMethodsEnum.INVITE;
                inviteHeader.UserAgent = "unit test";
                inviteRequest.Header = inviteHeader;

                SIPViaHeader viaHeader = new SIPViaHeader(localContact.Address.ToString(), localContact.Port,CallProperties.CreateBranchId(), SIPProtocolsEnum.UDP);
                inviteRequest.Header.Via.PushViaHeader(viaHeader);

                //inviteRequest.Body = inviteBody;
                //inviteRequest.Header.ContentLength = inviteBody.Length;
                inviteRequest.Header.ContentType = "application/sdp";

                return inviteRequest;
            }

        }

        #endif

        #endregion
    }
}
